use std;
use std::collections::HashSet;

fn main() {
	use my::TuringMachine;
	use my::Head;
	use my::Role;
	use my::Action::*;
	TuringMachine {
		cycles: 0,
		head: Head{
			pos: 0,
			state: "q0",
			roles: vec![
				Role("q0", '0', "q1", 'X', R),
				Role("q1", '0', "q1", '0', R),
				Role("q2", '0', "q2", '0', L),
				Role("q1", '1', "q2", 'Y', L),
				Role("q2", 'X', "q0", 'X', R),
				Role("q0", 'Y', "q3", 'Y', R),
				Role("q1", 'Y', "q1", 'Y', R),
				Role("q2", 'Y', "q2", 'Y', L),
				Role("q3", 'Y', "q3", 'Y', R),
				Role("q3", ' ', "q4", ' ', R),
			],
		},
		tape: "000111".to_string(),
		accepts: HashSet::from(["q4"]),
	}.run();
	println!("<<<<<main() END>>>>>");
}

mod my {
	use crate::HashSet;

	#[derive(Debug)]
	pub struct TuringMachine<'a> {
		pub cycles: usize,
		pub head: Head<'a>,
		pub tape: Tape,
		pub accepts: HashSet<State<'a>>,
	}
	impl TuringMachine<'_> {

		pub fn run(&mut self, ){
			let ref_roles : &Vec<Role> = &self.head.roles.clone();
			loop {
				self.display();
				let _old_state = self.head.state;
				let _tape  = &mut self.tape;
				let mut _matched_any_role = false;
				for Role(s0,c0,s1,c1,act) in ref_roles {

					if (*s0 == _old_state) &&
						(*c0 == _tape
									.chars()
									.nth(self.head.pos)
									.unwrap())
					{
						if self.head.pos == 0 && *act == Action::L {
							self.say_halt(&Halt::UndefinedPos);
							return;
						} else if self.accepts.contains(_old_state) {
							self.say_halt(&Halt::Accepted(&_old_state));
							return;
						}
						else
						{
							{
								let _state = &mut self.head.state;
								*_state = s1;
							}

							change_nth_unicode_char(_tape, self.head.pos.clone(), c1.clone());

							self.mov (&act);

							self.cycles += 1;

							_matched_any_role = true;

							break;
						}
					}
				}
				if !_matched_any_role {
					self.say_halt(&Halt::Undefined(&self.head.state, &self.get_current_char()));
					return;
				}
			}
		}
		fn say_halt (&self, reason: &Halt) {
			//use Halt::*;
			println!("HALT for {:?}", *reason);
			if self.accepts.contains(&self.head.state) {
				println!("ACCEPTED!");
			} else {
				println!("REJECTED!");
			}
		}
		fn mov (&mut self, _act: &Action){
			match *_act {
				Action::L => {
					self.head.pos -= 1;
				},
				Action::R => {
					self.head.pos += 1;
					let tape = &mut self.tape;
					if tape.chars().count() <= self.head.pos {
						*tape += " ";
					}
				},
			}
		}
		fn display (&self) {
			println!("{}:", self.cycles);
			println!("{}", self.tape);
			println!("{}^({})",
					 (0..self.head.pos).map(|_| " ").collect::<String>(),
					 self.head.state);
		}
		fn get_current_char(&self) -> char {
			self.tape.chars()
				.nth(self.head.pos)
				.unwrap()
		}
	}
	#[derive(Debug)]
	pub struct Head<'a>{
		pub pos:    usize,
		pub state:  State<'a>,
		pub roles:  Vec<Role<'a>>,
	}
	impl Head<'_> {

	}

	#[derive(Clone,Debug)]
	pub struct Role<'a> (pub State<'a>, pub CurrentChar, pub State<'a>, pub ChangeToChar, pub Action);
	type CurrentChar   = char;
	type ChangeToChar  = char;
	type State<'a>    =  &'a str;
	type Tape=String;
	#[derive(Clone,PartialEq,Debug)]
	pub enum Action{L, R,}

	#[derive(Clone,PartialEq,Debug)]
	enum Halt<'a> {
		UndefinedPos,
		Accepted(&'a State<'a>),
		Undefined(&'a State<'a>, &'a CurrentChar)
	}

	fn change_nth_unicode_char(s: &mut String, n: usize, ch: char){
		let (start, nth_char) = s.char_indices().nth(n).unwrap();
		s.replace_range(start..start + nth_char.len_utf8(), &ch.to_string());
	}
}
